﻿
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using Enferno.Public.Web.Mappers.ProductProfiles;
using Enferno.Public.Web.Models;
using Enferno.StormApiClient.Expose;
using Enferno.StormApiClient.ExposeProxy;
using Enferno.StormApiClient.Products;
using Enferno.Web.StormUtils;
using Microsoft.Practices.ObjectBuilder2;

namespace Enferno.Public.Web.Builders
{
    public class ProductListBuilder :
        ProductListCustomProductItemBuilder<ProductItemModel>
    {
        public ProductListBuilder(): base() {}
        public ProductListBuilder(ISiteRules rules): base(rules) {}
    }

    public class ProductListCustomProductItemBuilder<TProductItem> : ProductListCustomVariantBuilder<TProductItem, VariantModel>
        where TProductItem: ProductItemCustomVariantModel<VariantModel>
    {
        public ProductListCustomProductItemBuilder(): base() {}
        public ProductListCustomProductItemBuilder(ISiteRules rules) : base(rules) { }
    }

    public class ProductListCustomVariantBuilder<TProductItemModel, TVariantModel> :
        ProductListBaseBuilder<
            ProductListCustomVariantModel<TVariantModel, TProductItemModel>,
            TProductItemModel,
            TVariantModel,
            FilterModel,
            ProductFileModel,
            ProductManufacturerModel,
            PriceModel,
            OnHandStatusModel,
            ParametricModel,
            ParametricValueModel,
            FilterBaseBuilder<FilterModel>>
        where TProductItemModel : ProductItemCustomVariantModel<TVariantModel>
        where TVariantModel :
            VariantBaseModel<OnHandStatusModel, PriceModel, ProductFileModel, ParametricModel, ParametricValueModel>
    {
        public ProductListCustomVariantBuilder() : base() {}
        public ProductListCustomVariantBuilder(ISiteRules rules) : base(rules) {}
    }

    public class ProductListBaseBuilder<
        TProductListModel, 
        TProductItemModel, 
        TVariantModel, 
        TFilterModel,
        TProductFileModel, 
        TProductManufacturerModel, 
        TPriceModel, 
        TOnHandStatusModel,
        TParametricModel, 
        TParametricValueModel,
        TFilterBuilder> : BuilderBase
        where TProductListModel: ProductListBaseModel<TProductItemModel, TFilterModel, TVariantModel, TProductFileModel, TProductManufacturerModel, TPriceModel, TOnHandStatusModel, TParametricModel, TParametricValueModel>, new()
        where TProductItemModel: ProductItemBaseModel<TVariantModel, TProductFileModel, TProductManufacturerModel, TPriceModel, TOnHandStatusModel, TParametricModel, TParametricValueModel>
        where TVariantModel: VariantBaseModel<TOnHandStatusModel, TPriceModel, TProductFileModel, TParametricModel, TParametricValueModel>
        where TFilterModel: FilterModel, new()
        where TProductFileModel: ProductFileModel, new()
        where TProductManufacturerModel: ProductManufacturerModel
        where TPriceModel: PriceModel, new()
        where TOnHandStatusModel: OnHandStatusModel
        where TFilterBuilder: FilterBaseBuilder<TFilterModel>, new() 
        where TParametricModel: ParametricBaseModel<TParametricValueModel>, new()
        where TParametricValueModel: ParametricValueModel, new()
    {
        private TProductListModel model;

        private bool HasParametricFilters { get; set; }

        public ProductListBaseBuilder()
        {            
        }

        public ProductListBaseBuilder(ISiteRules rules)
            : base(rules)
        {            
        }

        public TProductListModel BuildProductList(ProductItemPagedList productList, FilterList filters = null)
        {
            return productList == null ? null : MapProductList(productList, filters);
        }

        private TProductListModel MapProductList(ProductItemPagedList productList, FilterList filters)
        {
            model = new TProductListModel
            {
                ItemCount = productList.ItemCount,
                Items = new List<TProductItemModel>(),                
                Filters = new List<TFilterModel>(),
            };

            if (productList.ItemCount == 0) return model;

            if (filters != null && filters.Count > 0)
            {
                var filterBuilder = new TFilterBuilder();
                model.Filters.AddRange(filterBuilder.BuildFilters(filters));
                HasParametricFilters = model.Filters.Count > 0 && model.Filters.Max(f => f.Id > (int)FilterCategory.OnHand);
            }

            model.Items = MapProductItems(productList.Items);
            return model;
        }

        private List<TProductItemModel> MapProductItems(IEnumerable<ProductItem> productItems)
        {
            return productItems.GroupBy(i => i.GroupByKey).Select(MapProductItemModel).ToList();
        }

        private TProductItemModel MapProductItemModel(IGrouping<string, ProductItem> productItemGroup)
        {
            var productItem = productItemGroup.First();
            var itemModel = Mapper.Map<ProductItem, TProductItemModel>(productItem);

            MapProductParametrics(productItem, itemModel.Parametrics);
            MapVariants(productItemGroup, itemModel);

            itemModel.Price = MapMinPrice(productItemGroup);
            itemModel.Price.IsFromPrice = productItemGroup.Max(v => v.Price) != productItemGroup.Min(v => v.Price);

            itemModel.OnHandStatus = itemModel.Variants.Min(x => x.OnHandStatus);

            return itemModel;
        }

        private static TPriceModel MapMinPrice(IGrouping<string, ProductItem> productItemGroup)
        {
            var productItem = productItemGroup.First(g => g.Price == productItemGroup.Min(v => v.Price));
            return MapPrice<TPriceModel>(productItem);
        }

        private void MapVariants(IEnumerable<ProductItem> variants, TProductItemModel itemModel)
        {
            foreach (var variant in variants)
            {
                AddProductImage(ProductFileType.VariantImage, variant.VariantImageKey, variant.Name, itemModel.Files);
                var variantModel = Mapper.Map<ProductItem, TVariantModel>(variant);

                MapVariantParametrics(variant, variantModel.VariantParametrics);
                MapParametrics(variant, variantModel.Parametrics);

                itemModel.Variants.Add(variantModel);
            }
        }

        private static void MapProductParametrics(ProductItem productItem, ICollection<TParametricModel> parametrics)
        {
            var variantParametricIds = GetVariantParametricIds(productItem);
            var allParametrics = GetAllParametrics(productItem);
            allParametrics.RemoveAll(i => variantParametricIds.Contains(i.Id));

            MapParametrics(parametrics, allParametrics);
        }

        private void MapParametrics(ProductItem productItem, ICollection<TParametricModel> parametrics)
        {
            if (!HasParametricFilters) return;

            var variantParametricIds = GetVariantParametricIds(productItem);
            var allParametrics = GetAllParametrics(productItem);
            allParametrics.RemoveAll(i => variantParametricIds.Contains(i.Id) || !IsFilterParametric(i));

            MapParametrics(parametrics, allParametrics);
        }

        private static void MapVariantParametrics(ProductItem productItem, ICollection<TParametricModel> parametrics)
        {
            var variantParametricIds = GetVariantParametricIds(productItem);
            var allParametrics = GetAllParametrics(productItem);
            allParametrics.RemoveAll(i => !variantParametricIds.Contains(i.Id));

            MapParametrics(parametrics, allParametrics);
        }

        private static List<IdValue> GetAllParametrics(ProductItem productItem)
        {
            var allParametrics = new List<IdValue>();
            AppendParametrics(productItem.ParametricListSeed, allParametrics);
            AppendParametrics(productItem.ParametricMultipleSeed, allParametrics);
            AppendParametrics(productItem.ParametricValueSeed, allParametrics);
            return allParametrics;
        }
        
        private bool IsFilterParametric(IdValue parametric)
        {
            return model.Filters.Any(f => f.Id == parametric.Id);
        }

        private static List<int> GetVariantParametricIds(ProductItem productItem)
        {
            var variantParametricIds = new List<int>();
            if (!string.IsNullOrWhiteSpace(productItem.VariantParametricSeed) && productItem.VariantParametricSeed.Split(',').Any())
            {
                variantParametricIds.AddRange(productItem.VariantParametricSeed.Split(',').Select(int.Parse));
            }
            return variantParametricIds;
        }

        private static void MapParametrics(ICollection<TParametricModel> parametrics, IEnumerable<IdValue> allParametrics)
        {
            foreach (var p in allParametrics)
            {
                TParametricModel parametricModel;
                var pi = ApplicationDictionary.Instance.ParametricInfo(p.Id, StormContext.CultureCode);
                if (pi == null) continue;
                var existing = parametrics.SingleOrDefault(x => x.Id == pi.Id);
                if (pi.Type == ParametricType.ListValue || pi.Type == ParametricType.MultiValue)
                {
                    var pv = ApplicationDictionary.Instance.ParametricValue(pi.Type, int.Parse(p.Value), StormContext.CultureCode);
                    if (pv == null) continue;
                    var value = new TParametricValueModel
                    {
                        Id = pv.Id,
                        Name = pv.Name,
                        Description = pv.Description,
                        Code = pv.Code,
                        ImageUrl = GetImageUrl(pv.ImageKey),
                        Value = pv.Name,
                        SortOrder = pv.SortOrder
                    };

                    if (existing != null)
                    {
                        existing.Values.Add(value);
                    }
                    else
                    {
                        parametricModel = new TParametricModel
                        {
                            Id = pi.Id,
                            Name = pi.Name,
                            Description = pi.Description,
                            Uom = pi.Uom,
                            ValueType = pi.ValueType,
                            Values = new List<TParametricValueModel> { value }
                        };
                        parametrics.Add(parametricModel);
                    }
                }
                else
                {
                    if (existing != null)
                    {
                        existing.Values.Add(new TParametricValueModel { Value = p.Value });
                    }
                    else
                    {
                        parametricModel = new TParametricModel
                        {
                            Id = pi.Id,
                            Name = pi.Name,
                            Description = pi.Description,
                            Uom = pi.Uom,
                            ValueType = pi.ValueType,
                            Values = new List<TParametricValueModel> { new TParametricValueModel { Value = p.Value } }
                        };
                        parametrics.Add(parametricModel);
                    }
                }
            }

            parametrics.ForEach(p => p.Values.Sort((p1, p2) => p1.SortOrder.CompareTo(p2.SortOrder)));
        }

        private static void AppendParametrics(string parametrics, List<IdValue> allParametrics)
        {
            if (string.IsNullOrWhiteSpace(parametrics)) return;
            allParametrics.AddRange(parametrics.Split(',').Select(parametric => parametric.Split(':')).Select(p => new IdValue {Id = int.Parse(p[0]), Value = p[1]}));
        }        
    }
}
